﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;
using Devonline.Core;
using Devonline.Entity;

namespace Devonline.Http
{
    public static class TypeExtensions
    {
        /// <summary>
        /// 获取一个对象中对于类型 T 的引用的外键名称集合
        /// </summary>
        /// <typeparam name="T">类型参数</typeparam>
        /// <param name="type">包含T类型引用的对象</param>
        /// <returns>外键名集合</returns>
        public static List<string> GetForeignKeys<TEntitySet, TKey>(this Type type) where TEntitySet : IEntitySet<TKey>
        {
            var properties = type?.GetProperties().Where(x => x.PropertyType.IsFromType<IEntitySet<TKey>>()).ToList();
            if (properties.IsNotNullOrEmpty())
            {
                var list = new List<string>();
                foreach (var prop in properties)
                {
                    var fk = prop.GetCustomAttribute<ForeignKeyAttribute>();
                    if (fk != null)
                    {
                        list.Add(fk.Name);
                    }
                    else
                    {
                        list.Add(prop.Name + nameof(IEntitySet<TKey>.Id));
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// 获取一个对象中对于类型 t 的引用的外键名称集合
        /// </summary>
        /// <param name="type">包含类型 t 引用的对象</param>
        /// <param name="t">类型 t</param>
        /// <returns>外键名集合</returns>
        public static List<string> GetForeignKeys<TKey>(this Type type, Type t)
        {
            var properties = type?.GetProperties().Where(x => x.PropertyType.IsFromType(t)).ToList();
            if (properties.IsNotNullOrEmpty())
            {
                var list = new List<string>();
                foreach (var prop in properties)
                {
                    var fk = prop.GetCustomAttribute<ForeignKeyAttribute>();
                    if (fk != null)
                    {
                        list.Add(fk.Name);
                    }
                    else
                    {
                        list.Add(prop.Name + nameof(IEntitySet<TKey>.Id));
                    }
                }
            }

            return null;
        }
        /// <summary>
        /// 获取对象obj中对于类型 TEntitySet 的第一个引用的外键名称
        /// </summary>
        /// <typeparam name="TEntitySet">类型参数</typeparam>
        /// <param name="type">包含类型 TEntitySet 引用的对象</param>
        /// <returns>外键名</returns>
        public static string GetForeignKey<TEntitySet, TKey>(this Type type) where TEntitySet : IEntitySet<TKey>
        {
            var propertyInfo = type?.GetProperties().FirstOrDefault(x => x.PropertyType.IsFromType<TEntitySet>());
            if (propertyInfo != null)
            {
                var foreignKey = propertyInfo.GetCustomAttribute<ForeignKeyAttribute>();
                if (foreignKey != null)
                {
                    return foreignKey.Name;
                }

                return propertyInfo.Name + nameof(IEntitySet<TKey>.Id);
            }

            return null;
        }
        /// <summary>
        /// 获取类型 type 中对于类型 t 的第一个引用的外键名称
        /// </summary>
        /// <param name="type">包含类型 t 引用的对象</param>
        /// <param name="t">类型 t</param>
        /// <returns>外键名</returns>
        public static string GetForeignKey<TKey>(this Type type, Type t)
        {
            var propertyInfo = type?.GetProperties().FirstOrDefault(x => x.PropertyType.IsFromType(t));
            if (propertyInfo != null)
            {
                var foreignKey = propertyInfo.GetCustomAttribute<ForeignKeyAttribute>();
                if (foreignKey != null)
                {
                    return foreignKey.Name;
                }

                return propertyInfo.Name + nameof(IEntitySet<TKey>.Id);
            }

            return null;
        }

        /// <summary>
        /// 根据外键属性获取导航属性 
        /// 如获取 Person 的外键 IdCardId 对应的导航属性方式为: idCardIdPropertyInfo.GetNavigationPropertyByForeignKey() => IdCard
        /// 如获取 PersonAdditional 的外键 PersonId 对应的导航属性方式为: personIdPropertyInfo.GetNavigationPropertyByForeignKey() => Person
        /// 外键对于的导航属性为将外键设置为 ForeignKey 特性的属性或者末尾加上 Id 等于外键列名的属性
        /// </summary>
        /// <param name="property">外键属性</param>
        /// <returns>导航属性</returns>
        public static PropertyInfo GetNavigationPropertyByForeignKey<TKey>(this PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var type = property.DeclaringType;
            var prop = type.GetProperties().FirstOrDefault(x => x.GetCustomAttributes<ForeignKeyAttribute>().Any(a => a.Name == property.Name));
            if (prop == null)
            {
                prop = type.GetProperty(property.Name.Substring(0, property.Name.Length - nameof(IEntitySet<TKey>.Id).Length));
            }

            return prop;
        }
        /// <summary>
        /// 根据导航属性获取外键属性
        /// 如获取 Person 的导航属性 IdCard 对应的外键属性方式为: idCardPropertyInfo.GetForeignKeyByNavigationProperty() => IdCardId
        /// 如获取 PersonAdditional 的导航属性 Person 对应的外键属性方式为: personPropertyInfo.GetForeignKeyByNavigationProperty() => PersonId
        /// 外键对于的导航属性为将外键设置为 ForeignKey 特性的属性或者末尾加上 Id 等于外键列名的属性
        /// </summary>
        /// <param name="property">导航属性</param>
        /// <returns>外键属性</returns>
        public static PropertyInfo GetForeignKeyByNavigationProperty<TKey>(this PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            var type = property.DeclaringType;
            var foreignKey = type.GetForeignKey<TKey>(property.PropertyType);
            if (foreignKey.IsNotNullOrEmpty())
            {
                return type.GetProperty(foreignKey);
            }

            return null;
        }

        /// <summary>
        /// 获取由 NavigableAttribute 指定的导航属性列表
        /// </summary>
        /// <param name="type">要获取NavigableAttribute类型</param>
        /// <returns>对象的NavigableAttribute指定的列</returns>
        public static List<PropertyInfo> GetNavigationProperties<TKey>(this Type type)
        {
            return type?.GetProperties().Where(x => x.HasAttribute<NavigableAttribute>() && (x.PropertyType.IsFromType<IEntitySet<TKey>>() || x.PropertyType.IsFromType<IEnumerable<IEntitySet<TKey>>>())).ToList();
        }
        /// <summary>
        /// 获取由 NavigableAttribute 指定的非导航属性列的值
        /// </summary>
        /// <param name="type">要获取NavigableAttribute类型</param>
        /// <returns>对象的NavigableAttribute指定的列</returns>
        public static PropertyInfo GetNavigationProperty<TKey>(this Type type)
        {
            return type?.GetProperties().FirstOrDefault(x => x.HasAttribute<NavigableAttribute>() && !x.PropertyType.IsFromType<IEntitySet<TKey>>() && !x.PropertyType.IsFromType<IEnumerable<IEntitySet<TKey>>>());
        }

        /// <summary>
        /// 获取当前模型对象程序集中所有可缓存类型
        /// </summary>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="type"></param>
        /// <returns></returns>
        public static List<Type> GetCacheTypes<TKey>(this Type type)
        {
            var types = type?.Assembly.GetTypes().Where(x => x.HasAttribute<CacheableAttribute>() && x.IsFromType<IEntitySet<TKey>>()).ToList();
            var cacheTypes = types.Where(x => x.GetProperties().All(a => !a.HasAttribute<NavigableAttribute>())).ToList();
            types = types.Where(x => !cacheTypes.Contains(x)).ToList();
            GetNavigableTypes(types, cacheTypes);
            return cacheTypes;
        }
        /// <summary>
        /// 获取类型的导航属性列表
        /// </summary>
        /// <param name="types"></param>
        /// <param name="cacheTypes"></param>
        private static void GetNavigableTypes(List<Type> types, List<Type> cacheTypes)
        {
            if (types.IsNotNullOrEmpty())
            {
                var temps = new List<Type>();
                foreach (var type in types)
                {
                    if (type.GetProperties().Where(x => x.HasAttribute<NavigableAttribute>()).All(x => cacheTypes.Contains(x.PropertyType.GetGenericType())))
                    {
                        temps.Add(type);
                    }
                }

                if (temps.IsNotNullOrEmpty())
                {
                    foreach (var type in temps)
                    {
                        types.Remove(type);
                        cacheTypes.Add(type);
                    }

                    GetNavigableTypes(types, cacheTypes);
                }
            }
        }
    }
}
